package main

import (
	"context"
	"fmt"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	yaml "gopkg.in/yaml.v2"
	"httpserver/src/metrics"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"os/signal"
	"runtime"
	"strings"
	"syscall"
	"time"
)

const instruction string = `欢迎来到 cncamp 作业之 HTTPServer，路径功能描述如下:
    - / 说明页
    - /header 根据配置，将数据写入响应http header
    - /version 读取version环境变量，写入http header
    - /log 写入日志
    - /healthz 探活检查`

type Conf struct {
	Log struct {
		Loglevel string `yaml:"loglevel"`
		Logdir   string `yaml:"log_dir"`
	}
	Header struct {
		Label string `yaml:"label"`
	}
}

//全局变量
//全局配置
var gconf *Conf

//日志输出
var sugarLogger *zap.SugaredLogger

/*
handler函数，用于处理htto请求
*/
func homehandler(w http.ResponseWriter, r *http.Request) {
	sugarLogger.Info("===================== homehandler =======================")
	fmt.Fprintln(w, instruction)
	sugarLogger.Info("首页返回", zap.String("header", fmt.Sprint(r.Header)))
}

func headerhandler(w http.ResponseWriter, r *http.Request) {
	sugarLogger.Info("===================== headerhandler =======================")
	//每次都重新读取一下配置文件
	tmpconf := new(Conf)
	//yamlFile, err := ioutil.ReadFile("/app/conf/conf.yaml")
	yamlFile, err := ioutil.ReadFile("conf/conf.yaml")
	if err != nil {
		sugarLogger.Errorf("读取配置文件%s失败！错误:%v \n", "/app/conf/conf.yaml", err)
	}
	err = yaml.Unmarshal(yamlFile, tmpconf)
	if err != nil {
		if err != nil {
			sugarLogger.Errorf("解析文件%s失败！错误:%v \n", "/app/conf/conf.yaml", err)
		}
	}
	fmt.Println(*tmpconf)
	//写入全局系统配置获取的label请求头
	if gconf.Header.Label != "" {
		w.Header().Set("label", tmpconf.Header.Label)
		fmt.Fprintln(w, tmpconf.Header.Label)
		sugarLogger.Infof("返回header: %s\n", tmpconf.Header)
	}
}

func healthz(w http.ResponseWriter, r *http.Request) {
	sugarLogger.Info("===================== healthz =======================")
	fmt.Fprintln(w, "ok")
	sugarLogger.Info("返回ok")
}
func versionhandler(w http.ResponseWriter, r *http.Request) {
	sugarLogger.Info("===================== versionhandler =======================")
	//写入version到header
	version := os.Getenv("version")
	w.Header().Set("version", version)
	fmt.Fprintln(w, "ok")
	sugarLogger.Infof("返回ok version: %s \n", version)
}

/*
工具类函数
*/
func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}
func buildDefaultLogDir() (string, error) {
	pwdstr, _ := os.Getwd()
	var abspathbuilder strings.Builder
	abspathbuilder.WriteString(pwdstr)
	switch runtime.GOOS {
	case "windows":
		abspathbuilder.WriteString("\\")
	case "linux":
		abspathbuilder.WriteString("/")
	}
	abspathbuilder.WriteString("logs")
	logdir := abspathbuilder.String()
	if exitsts, err := PathExists(logdir); err != nil {
		return "", err
	} else if !exitsts {
		fmt.Printf("日志目录%s不存在，创建目录！\n", logdir)
		err = os.MkdirAll(logdir, 0666)
		return "", err
	}
	return logdir, nil
}

//func InitLogger() {
//	writeSyncer := getLogWriter()
//	encoder := getEncoder()
//	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
//
//	logger := zap.New(core)
//	sugarLogger = logger.Sugar()
//}

func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	return zapcore.NewConsoleEncoder(encoderConfig)
}

func getLogWriter(logdir string) zapcore.WriteSyncer {
	var logfile string
	var abspathbuilder strings.Builder
	abspathbuilder.WriteString(logdir)
	if strings.Contains(runtime.GOOS, "windows") {
		abspathbuilder.WriteString("\\")
		abspathbuilder.WriteString("httpserver.log")
		logfile = abspathbuilder.String()
	} else {
		abspathbuilder.WriteString("/")
		abspathbuilder.WriteString("httpserver.log")
		logfile = abspathbuilder.String()
	}
	file, _ := os.Create(logfile)
	syncer := zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(file))
	return syncer
}

/*
初始化函数
*/
//解析全局变量gconf，进行初始化,初始化日志和header配置
func init() {

	//str, _ := os.Getwd()
	//fmt.Println(str)

	gconf = new(Conf)
	//yamlFile, err := ioutil.ReadFile("/app/conf/conf.yaml")
	yamlFile, err := ioutil.ReadFile("conf/conf.yaml")
	if err != nil {
		panic(fmt.Sprintf("yamlFile.Get err %v", err))
	}
	err = yaml.Unmarshal(yamlFile, gconf)
	if err != nil {
		panic(fmt.Sprintf("Unmarshal: %v when to struct", err))
	}
	fmt.Println(*gconf)
	fmt.Println("===================")

	//日志初始化
	var logdir string
	fmt.Println("初始化日志")
	if len(gconf.Log.Logdir) > 0 {
		logdir = gconf.Log.Logdir
		if strings.HasPrefix(logdir, "/") && strings.Contains(runtime.GOOS, "windows") {
			fmt.Println("检测到操作系统为windows，而配置日志目录为linux，使用默认日志路径 $(pwd)/logs")
			logdir, err = buildDefaultLogDir()
			if err != nil {
				panic(err)
			}
		} else {
			if exits, err := PathExists(logdir); err != nil {
				panic(err)
			} else if !exits {
				err = os.MkdirAll(logdir, 0666)
				if err != nil {
					panic(err)
				}
			}
		}

	} else {
		fmt.Printf("未配置日志输出目录，采用默认目录 %s\n", "$(pwd)/logs")

	}
	fmt.Printf("日志输出路径为： %s \n", logdir)
	//设置日志输出级别
	loglev := zapcore.InfoLevel
	if len(gconf.Log.Loglevel) == 0 {
		fmt.Println("使用默认日志输出级别info")
		loglev = zapcore.InfoLevel
	} else {
		level := strings.ToLower(gconf.Log.Loglevel)
		loglevemap := map[string]zapcore.Level{"debug": zapcore.DebugLevel, "info": zapcore.InfoLevel, "warn": zapcore.WarnLevel, "error": zapcore.WarnLevel, "panic": zapcore.PanicLevel, "fatal": zapcore.FatalLevel}

		if _, ok := loglevemap[level]; ok {
			loglev = loglevemap[level]
		}
	}
	fmt.Printf("日志级别为%d \n", loglev)
	//初始化日志
	writeSyncer := getLogWriter(logdir)
	encoder := getEncoder()
	core := zapcore.NewCore(encoder, writeSyncer, loglev)
	logger := zap.New(core)
	sugarLogger = logger.Sugar()

	sugarLogger.Info("日志初始化完成")

}

/*
主函数
*/

func main() {
	//注册metrics
	metrics.Register()
	// 自定义路由
	mux := http.NewServeMux()

	mux.HandleFunc("/", homehandler)
	mux.HandleFunc("/header", headerhandler)
	mux.HandleFunc("/version", versionhandler)
	mux.HandleFunc("/healthz", healthz)
	mux.HandleFunc("/hello", rootHandler)
	mux.Handle("/metrics", promhttp.Handler())

	//设置服务器,孟老师ppt里面，没有明确定义端口，这里选用8080
	srv := http.Server{
		Addr:    ":8080",
		Handler: mux,
	}

	// 确保优雅的退出
	processed := make(chan struct{})
	go func() {
		c := make(chan os.Signal, 1)
		signal.Notify(c, os.Interrupt)
		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
		<-c
		ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
		defer cancel()
		//为server设置一个用于优雅退出的上下文
		if err := srv.Shutdown(ctx); nil != err {
			sugarLogger.Fatal("server shutdown failed, err:", zap.String("error", fmt.Sprint(err)))
		}
		sugarLogger.Info("server gracefully shutdown")
		close(processed)
	}()

	// 启动服务器
	err := srv.ListenAndServe()
	if http.ErrServerClosed != err {
		sugarLogger.Fatal("服务优雅关闭, err :", zap.String("error", fmt.Sprint(err)))
	}

	// 利用chan阻塞主进程
	<-processed
}

func randInt(min int, max int) int {
	rand.Seed(time.Now().UTC().UnixNano())
	return min + rand.Intn(max-min)
}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	sugarLogger.Info("entering root handler")
	timer := metrics.NewTimer()
	defer timer.ObserveTotal()
	user := r.URL.Query().Get("user")
	delay := randInt(10, 2000)
	time.Sleep(time.Millisecond * time.Duration(delay))
	if user != "" {
		io.WriteString(w, fmt.Sprintf("hello [%s]\n", user))
	} else {
		io.WriteString(w, "hello [stranger]\n")
	}
	io.WriteString(w, "===================Details of the http request header:============\n")
	for k, v := range r.Header {
		io.WriteString(w, fmt.Sprintf("%s=%s\n", k, v))
	}
	sugarLogger.Infof("Respond in %d ms", delay)
}
